/*
This file is part of [ahttpserver] project. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/
#include "aconnect/types.hpp"

#include <assert.h>

#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>

#include "aconnect/util.string.hpp"

#include "settings_tags.inl"
#include "settings_loader.hpp"

#include "ahttp/http_support.hpp"


namespace algo = boost::algorithm;
namespace fs = boost::filesystem;


using aconnect::string_constptr;
using aconnect::string;

SettingsLoader::SettingsLoader() : 
	_directoryConfigFile (DefaultFiles::DirectoryConfigFile),
	_messagesFile (DefaultFiles::MessagesConfigFile),
	_logLevel (aconnect::Log::Debug), 				   
	_maxLogFileSize (aconnect::Log::MaxFileSize)
{
	_pluginsDirectory = std::string(ahttp::SettingsConstants::AppPathMark) + DefaultFiles::PluginsDirectory;
}


void SettingsLoader::loadFile (aconnect::string configFilePath) throw (settings_load_error)
{
	_configFilePath.swap(configFilePath);

	bool loadOkay = _xmlDocument.LoadFile ( _configFilePath.c_str() );

	if ( !loadOkay ) {
		throw settings_load_error ("Could not load settings file \"%s\". Error=\"%s\".",
			_configFilePath.c_str(),
			_xmlDocument.ErrorDesc());
	}

	TiXmlElement* root = _xmlDocument.RootElement( );
	assert ( root );
	
	if ( !aconnect::util::equals(root->Value(), SettingsTags::RootElement, false)  ) 
		throw settings_load_error ("Invalid setting root element");
}

void SettingsLoader::loadLoggerSettings () throw (settings_load_error)
{
	using namespace aconnect;

	TiXmlElement* root = _xmlDocument.RootElement( );
	assert ( root );
	
	TiXmlElement* logElement = root->FirstChildElement (SettingsTags::LogElement);
	if ( logElement ) 
	{
		// load level
		string_constptr strValue = logElement->Attribute( SettingsTags::LogLevelAttr );
		
		if (strValue) {
			if (stricmp (strValue, Log::CriticalMsg) == 0)
				_logLevel = Log::Critical;
			else if (stricmp (strValue, Log::ErrorMsg) == 0)
				_logLevel = Log::Error;
			else if (stricmp (strValue, Log::WarningMsg) == 0)
				_logLevel = Log::Warning;
			else if (stricmp (strValue, Log::InfoMsg) == 0)
				_logLevel = Log::Info;
			else 
				_logLevel = Log::Debug;
		}

		loadIntAttribute(logElement, SettingsTags::MaxFileSizeAttr, _maxLogFileSize);
		
		TiXmlElement* pathElement = logElement->FirstChildElement (SettingsTags::PathElement);
		
		if ( 0 != pathElement ) 
		{
			strValue = pathElement->GetText();
			if ( util::isNullOrEmpty(strValue) ) 
				throw settings_load_error ("Invalid log file template");
			_logFileTemplate = strValue;
		}
	
	} else {
		_logFileTemplate = DefaultFiles::LogFileTemplate;// default logs folder
	}

}


void SettingsLoader::load (ahttp::HttpServerSettings& settings, bool firstLoad) throw (settings_load_error)
{
	TiXmlElement* root = _xmlDocument.RootElement( );
	assert ( root );
	
	if (firstLoad)
	{
		TiXmlElement* serverElem = root->FirstChildElement (SettingsTags::ServerElement);
		assert (serverElem);
		if ( !serverElem ) 
			throw settings_load_error ("Cannot find <server> element");

		// server setup
		loadServerSettings (settings, serverElem);
	} 
	else 
	{
		// clear directories info
		settings.resetDirectoriesInfo();	
	}

	TiXmlElement* directoryElem = root->FirstChildElement (SettingsTags::DirectoryElement);
	if ( !directoryElem ) 
		throw settings_load_error ("At least one <%s> element must be", SettingsTags::DirectoryElement);

	std::vector <ahttp::DirectorySettings> directoriesList;
	do {
		ahttp::DirectorySettings ds = loadDirectory (settings, directoryElem);
		ds.parentServerSettings = &settings;

		directoriesList.push_back (ds );
		
		directoryElem = directoryElem->NextSiblingElement(SettingsTags::DirectoryElement);

	} while (directoryElem);

	
	settings.loadRootDirectory (directoriesList, 
		std::bind1st (std::mem_fun(&SettingsLoader::checkLocalSettingsFilePresence), this ));
	
}

void SettingsLoader::checkLocalSettingsFilePresence (ahttp::DirectorySettings* ds) throw (settings_load_error)
{
	assert (ds->parentServerSettings);
	
	fs::path dirConfigFile = fs::path (ds->realPath, fs::native) / fs::path(_directoryConfigFile, fs::portable_file_name);
	tryLoadLocalSettings (*ds->parentServerSettings, dirConfigFile.string(), *ds);
}


void SettingsLoader::loadServerSettings (ahttp::HttpServerSettings& settings, TiXmlElement* serverElem) throw (settings_load_error)
{
	using namespace aconnect;

	string_constptr strValue;
	string stringValue;
	port_type portValue;

	// version
	strValue = serverElem->Attribute (SettingsTags::VersionAttr);
	if (!util::isNullOrEmpty(strValue))
		settings.setServerVersion (strValue);

	// port
	if (!loadIntAttribute (serverElem, SettingsTags::PortAttr, portValue))
		throw settings_load_error ("Port number loading failed");
	else 
		settings.setPort(portValue);

	// command port
	if (!loadIntAttribute (serverElem, SettingsTags::CommandPortAttr, portValue))
		throw settings_load_error ("Command port number loading failed");
	else 
		settings.setCommandPort(portValue);

	loadIntAttribute(serverElem, SettingsTags::CommandSocketTimeoutAttr, settings.CommandSocketTimeout);

	// directory configuration file
	loadStringAttribute (serverElem, SettingsTags::DirectoryConfigFileAttr, _directoryConfigFile, true);
	// messages file
	loadStringAttribute (serverElem, SettingsTags::MessagesFileAttr, _messagesFile, true);
	// default plugins directory
	loadStringAttribute (serverElem, SettingsTags::PluginsDirAttr, _pluginsDirectory, true);

	// setup global plugins dir
	settings.updateAppLocationInPath (_pluginsDirectory);

	//==================================================================//
	//	TCP server settings												//
	//==================================================================//

	// IP address - OPTIONAL
	if (loadStringAttribute (serverElem, SettingsTags::IpAddressAttr, stringValue))
		if (! util::parseIpAddr(stringValue, settings.ConnectionSettings.ip)) 
			throw settings_load_error ("Invalid IP address in server config");

	// workers count - OPTIONAL
	loadIntAttribute (serverElem, SettingsTags::WorkersCountAttr, settings.ConnectionSettings.workersCount);

	// server stopping timeout - OPTIONAL
	loadIntAttribute (serverElem, SettingsTags::StopWaitTimeoutAttr, settings.ConnectionSettings.stopWaitTimeout);

	// pooling - OPTIONAL
	loadBoolAttribute (serverElem, SettingsTags::PoolingEnabledAttr, settings.ConnectionSettings.enablePooling);


	// read timeouts
	loadIntAttribute(serverElem, SettingsTags::ServerSocketTimeoutAttr, settings.ConnectionSettings.socketReadTimeout);
	loadIntAttribute(serverElem, SettingsTags::ServerSocketTimeoutAttr, settings.ConnectionSettings.socketWriteTimeout);

	// load keep-alive mode
	loadBoolAttribute (serverElem, SettingsTags::KeepAliveEnabledAttr, settings.IsKeepAliveEnabled);
	loadIntAttribute(serverElem, SettingsTags::KeepAliveTimeoutAttr, settings.KeepAliveTimeout);

	// gzip/deflate support
	loadBoolAttribute (serverElem, SettingsTags::GzipDeflateEncodingEnabledAttr, settings.IsGzipDeflateEnabled);
	
	// uploads directory setup
	loadStringAttribute (serverElem, SettingsTags::UploadsDirAttr, settings.GlobalUploadsDirectory, true);
	loadIntAttribute (serverElem, SettingsTags::UploadCreationTriesCountAttr, settings.UploadCreationTriesCount);
	// create uploads directory if defined
	settings.setupGlobalUploadsDir();

	// load locale an apply it,
	// locale should be defined to perform correct MBSTR->WIDE conversion
	if (loadStringAttribute (serverElem, SettingsTags::LocaleAttr, stringValue))
		if (!stringValue.empty())
			settings.applyLocale(stringValue);
	
		
	//==================================================================//
	//	HTTP server settings											//
	//==================================================================//
	
	loadIntAttribute (serverElem, SettingsTags::ResponseBufferSizeAttr, settings.ResponseBufferSize);

	loadIntAttribute (serverElem, SettingsTags::MaxChunkSizeAttr, settings.MaxChunkSize);
	
	// root directory
	if ( !loadStringAttribute (serverElem, SettingsTags::RootAttr, stringValue) ) 
		throw settings_load_error ("Invalid root directory name");
	else
		settings.setRootDirectoryName (stringValue);
	
	TiXmlElement* mimeTypesElement = serverElem->FirstChildElement (SettingsTags::MimeTypesElement);
	if (mimeTypesElement)
		loadMimeTypes (settings, mimeTypesElement);
	
	// read server's handlers registration info
	TiXmlElement* handlersElem = serverElem->FirstChildElement (SettingsTags::HandlersElement);
	if (handlersElem)
		loadPlugins (settings, handlersElem, ahttp::PluginHandler);

	// read server's modules registration info
	TiXmlElement* modulesElem = serverElem->FirstChildElement (SettingsTags::ModulesElement);
	if (modulesElem)
		loadPlugins (settings, modulesElem, ahttp::PluginModule);
	
	loadMessages (settings);
}


void SettingsLoader::loadMessages(ahttp::HttpServerSettings& settings)  throw (settings_load_error)
{
	fs::path msgFile = fs::path (settings.AppLocation, fs::native) / fs::path(_messagesFile, fs::portable_file_name);
	if (!fs::exists (msgFile))
		return;

	TiXmlDocument doc( msgFile.string().c_str() );

	if ( !doc.LoadFile() ) {
		throw settings_load_error ("Could not load messages file \"%s\". Error=\"%s\".",
			msgFile.string().c_str(),
			doc.ErrorDesc());
	}

	TiXmlElement* root = doc.RootElement( );

	if ( !aconnect::util::equals(root->Value(), SettingsTags::MessagesElement, false)  ) 
		throw settings_load_error ("Invalid messages file root element, file: %s",
			msgFile.string().c_str());

	string name;
	TiXmlElement* item = root->FirstChildElement (SettingsTags::MessageItemElement);

	while (item) 
	{
		if ( !loadStringAttribute(item, SettingsTags::NameAttr, name))
			throw settings_load_error ("<%s> does not have \"%s\" attribute, file: %s",
			SettingsTags::MessageItemElement, 
			SettingsTags::NameAttr, 
			msgFile.string().c_str());

		settings.loadMessage (name, item->GetText());
		
		item = item->NextSiblingElement (SettingsTags::MessageItemElement);
	}

}


void SettingsLoader::loadMimeTypes (ahttp::HttpServerSettings& settings, class TiXmlElement* mimeTypesElement) throw (settings_load_error)
{
	string_constptr filePath = mimeTypesElement->Attribute(SettingsTags::FileAttr);

	if ( filePath )
	{
		string filePathStr (filePath);

		settings.updateAppLocationInPath (filePathStr);

		TiXmlDocument doc;
		bool loaded = doc.LoadFile (filePathStr);

		if ( !loaded ) {
			throw settings_load_error ("Could not load MIME-types definition file \"%s\". Error=\"%s\".",
				filePathStr.c_str(),
				doc.ErrorDesc());
		}

		TiXmlElement* root = doc.RootElement( );
		assert ( root );
		if ( !aconnect::util::equals (root->Value(), SettingsTags::MimeTypesElement, false) ) 
			throw settings_load_error ("Invalid root element in MIME-types definition file");

		loadMimeTypes (settings, root);
	}

	// load sub-nodes

	TiXmlElement* typeElem = mimeTypesElement->FirstChildElement (SettingsTags::TypeElement);
	if ( !typeElem ) 
		return;

	string_constptr strType;
	string ext;
	
	do {
		// load name
		int getAttrRes = typeElem->QueryValueAttribute( SettingsTags::ExtAttr, &ext);
		if (getAttrRes == TIXML_NO_ATTRIBUTE)
			throw settings_load_error ("<%s> does not have \"%s\" attribute",
			SettingsTags::TypeElement, 
			SettingsTags::ExtAttr);

		strType = typeElem->GetText(); 
		if (strType) 
			settings.loadMimeType (ext, strType);

		typeElem = typeElem->NextSiblingElement (SettingsTags::TypeElement);

	} while (typeElem);
}

void SettingsLoader::loadPlugins (ahttp::HttpServerSettings& settings, class TiXmlElement* pluginsElement, ahttp::PluginType pluginType) throw (settings_load_error)
{
	using namespace aconnect;
	using namespace ahttp;

	TiXmlElement* pluginElem = pluginsElement->FirstChildElement (SettingsTags::RegisterElement);

	// make continuous numbering
	int index = (int) (settings.RegisteredHandlers.size() + settings.RegisteredModules.size());
	
	while (pluginElem) 
	{
		PluginInfo info;
		info.pluginIndex = index++;

		string pluginName = loadPluginRegistration (settings, pluginElem, info, pluginType);
		
		settings.loadPluginProperties (pluginName, info, pluginType);

		pluginElem = pluginElem->NextSiblingElement (SettingsTags::RegisterElement);
	}
}

string SettingsLoader::loadPluginRegistration (ahttp::HttpServerSettings& settings,
												class TiXmlElement* pluginElem, 
											   ahttp::PluginInfo& info, 
											   ahttp::PluginType pluginType) throw (settings_load_error)
{
	string pluginName;

	if (!loadStringAttribute(pluginElem, SettingsTags::NameAttr, pluginName, true))
		throw settings_load_error ("Global <%s> does not have \"%s\" attribute - it is required",
		SettingsTags::RegisterElement, 
		SettingsTags::NameAttr);

	if (pluginType == ahttp::PluginHandler) {

		if (loadStringAttribute(pluginElem, SettingsTags::DefaultExtAttr, info.defaultExtension)) {
			if (info.defaultExtension.empty())
				throw settings_load_error ("Handler \"%s\" has empty \"%s\" attribute",
				pluginName.c_str(), 
				SettingsTags::DefaultExtAttr);
		}

	} else if (pluginType == ahttp::PluginModule) {
		loadBoolAttribute(pluginElem, SettingsTags::GlobalAttr, info.global);
	}

	TiXmlElement* childElem = pluginElem->FirstChildElement(SettingsTags::PathElement);

	if (!childElem)
		throw settings_load_error ("Plugin \"%s\" has no <%s> element ", 
		pluginName.c_str(), 
		SettingsTags::PathElement);

	string_constptr path = childElem->GetText();

	if (aconnect::util::isNullOrEmpty(path))
		throw settings_load_error ("Plugin \"%s\" has empty <%s> element ", 
		pluginName.c_str(), 
		SettingsTags::PathElement);

	string pth = path;
	settings.updateAppLocationInPath (pth);

	fs::path pluginPath(pth, fs::native);

	if (!fs::exists (pluginPath)) {

		const std::string ext = fs::extension(pluginPath);
		if (ext.empty())
			pluginPath = fs::change_extension(pluginPath, DefaultFiles::PluginExtension);

		// check file in plugins dir
		pluginPath = fs::path (_pluginsDirectory, fs::native) / pluginPath;
		if (!fs::exists (pluginPath))
			throw settings_load_error ("Plugin library not found, tried \"%s\"", pluginPath.file_string().c_str());
	}

	info.pathToLoad = pluginPath.file_string();
	
	
	// load parameters
	childElem = pluginElem->FirstChildElement (SettingsTags::ParameterElement);

	string paramName;

	while (childElem) 
	{
		if ( !loadStringAttribute(childElem, SettingsTags::NameAttr, paramName, true) )
			throw settings_load_error ("<%s> for handler \"%s\" have no \"%s\" attribute",
			SettingsTags::ParameterElement, 
			pluginName.c_str(), 
			SettingsTags::NameAttr );

		string_constptr value = childElem->GetText();
		if (NULL != value)
			info.params[paramName] = value;

		childElem = childElem->NextSiblingElement (SettingsTags::ParameterElement);
	}

	return pluginName;
}


ahttp::DirectorySettings SettingsLoader::loadDirectory (ahttp::HttpServerSettings& settings, TiXmlElement* directoryElem) throw (settings_load_error)
{
	using namespace aconnect;
	assert (directoryElem);

	ahttp::DirectorySettings ds;
	string strValue;
	string_constptr strPtrValue;
	int getAttrRes = 0, 
		intAttr = 0;

	// load name
	getAttrRes = directoryElem->QueryValueAttribute( SettingsTags::NameAttr, &ds.name);
	if (getAttrRes != TIXML_SUCCESS)
		throw settings_load_error ("Directory does not have \"%s\" attribute",
		SettingsTags::NameAttr);

	// load browsing-enabled
	loadTristateAttribute (directoryElem, SettingsTags::BrowsingEnabledAttr, ds.browsingEnabled);
	loadTristateAttribute (directoryElem, SettingsTags::ShowHiddenFilesAttr, ds.showHiddenFiles);

	// load charset
	loadStringAttribute (directoryElem, SettingsTags::CharsetAttr, ds.charset);

	// load max-request-size
	if (loadIntAttribute (directoryElem, SettingsTags::MaxRequestSizeAttr, intAttr))
		ds.maxRequestSize = intAttr;

	// load browsing-enabled
	loadTristateAttribute (directoryElem, SettingsTags::EnableParentPathAccess, ds.enableParentPathAccess);

	// load parent
	getAttrRes = directoryElem->QueryValueAttribute( SettingsTags::ParentAttr, &strValue);
	if (getAttrRes == TIXML_SUCCESS) 
		ds.parentName = strValue;

	// path
	bool realPathDefined = false;
	TiXmlElement* pathElement = directoryElem->FirstChildElement (SettingsTags::PathElement);
	if (pathElement) {
		strPtrValue = pathElement->GetText();
		realPathDefined = true;

		if ( util::isNullOrEmpty(strPtrValue) ) 
			throw settings_load_error ("Empty path attribute for directory: %s", ds.name.c_str());

		ds.realPath = strPtrValue;

		settings.updateAppLocationInPath (ds.realPath);
	}

	// virtual-path
	pathElement = directoryElem->FirstChildElement (SettingsTags::VirtualPathElement);
	if (pathElement) {
		strPtrValue = pathElement->GetText();
		if ( !util::isNullOrEmpty(strPtrValue) )
			ds.virtualPath = strPtrValue;
	}

	// relative-path
	pathElement = directoryElem->FirstChildElement (SettingsTags::RelativePathElement);
	if (pathElement) {
		if (realPathDefined)
			throw settings_load_error ("<%s> and <%s> must not be defined together,\
									   directory: %s",
									   SettingsTags::PathElement,
									   SettingsTags::RelativePathElement,
									   ds.name.c_str());

		strPtrValue = pathElement->GetText();
		if ( !util::isNullOrEmpty(strPtrValue) ) 
			ds.relativePath = strPtrValue;
	}


	TiXmlElement* fileTemplate = directoryElem->FirstChildElement (SettingsTags::FileTemplateElement);
	TiXmlElement* directoryTemplate = directoryElem->FirstChildElement (SettingsTags::DirectoryTemplateElement);
	TiXmlElement* virtualDirectoryTemplate = directoryElem->FirstChildElement (SettingsTags::VirtualDirectoryTemplateElement);
	TiXmlElement* parentDirectoryTemplate = directoryElem->FirstChildElement (SettingsTags::ParentDirectoryTemplateElement);
	TiXmlElement* headerTemplate = directoryElem->FirstChildElement (SettingsTags::HeaderTemplateElement);
	TiXmlElement* footerTemplate = directoryElem->FirstChildElement (SettingsTags::FooterTemplateElement);

	if ( (fileTemplate && directoryTemplate && virtualDirectoryTemplate) 
		!= (fileTemplate || directoryTemplate || virtualDirectoryTemplate))
		throw settings_load_error ("<directory-template>, <virtual-directory-template> and <file-template> should be defined together, directory: %s", ds.name.c_str());

	if (fileTemplate) {
		strPtrValue = fileTemplate->GetText();				if (strPtrValue) ds.fileTemplate = strPtrValue;
		strPtrValue = directoryTemplate->GetText();			if (strPtrValue) ds.directoryTemplate = strPtrValue;
		strPtrValue = virtualDirectoryTemplate->GetText();	if (strPtrValue) ds.virtualDirectoryTemplate = strPtrValue;
	}
	
	if (parentDirectoryTemplate && (strPtrValue = parentDirectoryTemplate->GetText())) 
		ds.parentDirectoryTemplate = strPtrValue;
	
	if (headerTemplate && (strPtrValue = headerTemplate->GetText()))
		ds.headerTemplate = strPtrValue;
	
	if (footerTemplate && (strPtrValue = footerTemplate->GetText()) )
		ds.footerTemplate = strPtrValue;

	ds.processTemplates();

	loadDirectorySettings (settings, directoryElem, ds);

	return ds;
}

void SettingsLoader::loadDirectorySettings (ahttp::HttpServerSettings& settings, class TiXmlElement* directoryElem, ahttp::DirectorySettings& dirInfo) throw (settings_load_error) 
{
	using namespace ahttp;

	// load default documents
	TiXmlElement* defDocumentsElem = directoryElem->FirstChildElement (SettingsTags::DefaultDocumentsElement);
	if (defDocumentsElem)
		loadDirectoryIndexDocuments (defDocumentsElem, dirInfo);

	// load directory handlers
	TiXmlElement* handlersElem = directoryElem->FirstChildElement (SettingsTags::HandlersElement);
	if (handlersElem)
		loadDirectoryPlugins (settings, handlersElem, dirInfo, PluginHandler);

	// load directory modules
	TiXmlElement* modulesElem = directoryElem->FirstChildElement (SettingsTags::ModulesElement);
	if (modulesElem)
		loadDirectoryPlugins (settings, modulesElem, dirInfo, PluginModule);

	// load directory mappings
	TiXmlElement* mappings = directoryElem->FirstChildElement (SettingsTags::MappingsElement);
	if (mappings)
		loadDirectoryMappings (mappings, dirInfo);

	// load <directory-params>
	TiXmlElement* dirParams = directoryElem->FirstChildElement (SettingsTags::DirectoryParamsElement);
	
	if (dirParams) 
	{
		TiXmlElement* elem = dirParams->FirstChildElement (SettingsTags::ParameterElement);
		aconnect::string paramName;

		while (elem) {
			if ( !loadStringAttribute(elem, SettingsTags::NameAttr, paramName, true) )
				throw settings_load_error ("<%s> for directory parameters \"%s\" have no \"%s\" attribute",
				SettingsTags::ParameterElement, 
				dirInfo.name.c_str(), 
				SettingsTags::NameAttr );

			string_constptr value = elem->GetText();
			if (0 != value)
				dirInfo.customParams[paramName] = value;

			elem = elem->NextSiblingElement (SettingsTags::ParameterElement);
		}
	}
}

void SettingsLoader::loadDirectoryIndexDocuments (class TiXmlElement* documentsElement, ahttp::DirectorySettings& ds) throw (settings_load_error)
{
	using namespace ahttp;
	string_constptr strPtrValue;

	TiXmlElement* elem = documentsElement->FirstChildElement (SettingsTags::AddElement);
	while (elem) {
		if ( (strPtrValue = elem->GetText()) ) 
			ds.defaultDocuments.push_back (std::make_pair (true, strPtrValue));

		elem = elem->NextSiblingElement (SettingsTags::AddElement);
	}

	elem = documentsElement->FirstChildElement(SettingsTags::RemoveElement);
	while (elem) {
		if ( (strPtrValue = elem->GetText()) ) 
			ds.defaultDocuments.push_back (std::make_pair (false, strPtrValue));
		elem = elem->NextSiblingElement (SettingsTags::RemoveElement);
	}
}


void SettingsLoader::loadDirectoryPlugins (ahttp::HttpServerSettings& settings,
										   class TiXmlElement* pluginsElement, 
										   ahttp::DirectorySettings& dirInfo,
										   ahttp::PluginType pluginType) throw (settings_load_error)
{
	using namespace aconnect;
	using namespace ahttp;

	string ext, name, excludeExt;
	std::set<string> extList, excludeExtList;
	int getAttrRes;

	TiXmlElement* item = pluginsElement->FirstChildElement ();

	global_plugins_map& pluginsMap = (pluginType == PluginHandler ? settings.RegisteredHandlers : settings.RegisteredModules);
	directory_plugins_list& targetPluginsList = (pluginType == PluginHandler ? dirInfo.handlers : dirInfo.modules);

	// process add, remove, clear, register elements
	while (item) 
	{
		PluginRegistrationInfo regInfo;

		if (util::equals(item->Value(), SettingsTags::RegisterElement) )
		{
			PluginInfo info;
			info.pluginIndex = (int) (settings.RegisteredHandlers.size() + settings.RegisteredModules.size());

			// load plugin into server
			string pluginName = loadPluginRegistration (settings, item, info, pluginType);
			
			settings.loadPluginProperties (pluginName, info, pluginType);

			regInfo.pluginName = pluginName;
			regInfo.registrationType = PluginRegistration::Register;
			regInfo.pluginIndex = info.pluginIndex;

			if (pluginType == PluginHandler) {
				regInfo.processFunc = info.processRequestFunc;

				if (TIXML_NO_ATTRIBUTE != item->QueryValueAttribute( SettingsTags::ExtAttr, &ext))
					regInfo.extensions = HttpServerSettings::parseExtensions (ext);
				// load 'exclude-ext' from current tag
				if (TIXML_NO_ATTRIBUTE != item->QueryValueAttribute( SettingsTags::ExcludeExtAttr, &excludeExt))
					regInfo.excludedExtensions = HttpServerSettings::parseExtensions (excludeExt);

			} else {
				std::copy (&info.moduleCallbacks[0], 
					&info.moduleCallbacks[0] + ModuleCallbackTypesCount,
					&regInfo.moduleCallbacks[0]);
			}

			targetPluginsList.push_back ( regInfo );
		}
		else if (util::equals(item->Value(), SettingsTags::AddElement) )
		{
			getAttrRes = item->QueryValueAttribute( SettingsTags::NameAttr, &name);
			if (getAttrRes == TIXML_NO_ATTRIBUTE)
				throw settings_load_error ("<%s> does not have \"%s\" attribute, directory: %s",
				SettingsTags::RegisterElement, SettingsTags::NameAttr, dirInfo.name.c_str());

			global_plugins_map::iterator foundPlugin = pluginsMap.find(name);
			if (foundPlugin == pluginsMap.end())
				throw settings_load_error ("Plugin \"%s\" is not registered, directory: %s",
				name.c_str(), dirInfo.name.c_str());

			const PluginInfo& info = foundPlugin->second;

			if (pluginType == PluginHandler)
			{
				getAttrRes = item->QueryValueAttribute( SettingsTags::ExtAttr, &ext);
				
				if (info.defaultExtension.empty() && (getAttrRes == TIXML_NO_ATTRIBUTE || ext.empty() ))
					throw settings_load_error ("Handler \"%s\" has not link to file extension, directory: %s",
						name.c_str(), dirInfo.name.c_str());
				else if (ext.empty())
					ext = info.defaultExtension;

				extList = HttpServerSettings::parseExtensions (ext);

				// load 'exclude-ext' from current tag
				if (TIXML_NO_ATTRIBUTE != item->QueryValueAttribute( SettingsTags::ExcludeExtAttr, &excludeExt))
					excludeExtList = HttpServerSettings::parseExtensions (excludeExt);
					
			}

			directory_plugins_list::iterator it = std::find_if (targetPluginsList.begin(), 
				targetPluginsList.end(), ahttp::PluginInfoByNameFinder(name) );

			if (it == targetPluginsList.end()) 
			{
				regInfo.registrationType = PluginRegistration::Add;
				regInfo.pluginName = name;
				regInfo.pluginIndex = info.pluginIndex;

				if (pluginType == PluginHandler)
				{
					regInfo.processFunc = info.processRequestFunc;
					regInfo.extensions.insert(extList.begin(), extList.end());

					if (!info.defaultExtension.empty()) {
						std::set<string> extList = HttpServerSettings::parseExtensions (info.defaultExtension);
						regInfo.extensions.insert(extList.begin(), extList.end());
					}

					if (excludeExtList.size())
						regInfo.excludedExtensions.insert(excludeExtList.begin(), excludeExtList.end());
				}
				else
				{
					if (info.global)
						throw settings_load_error ("Global module registration config cannot be added to directory \"%s\"",
						dirInfo.name.c_str());

					std::copy (&info.moduleCallbacks[0], 
						&info.moduleCallbacks[0] + ModuleCallbackTypesCount,
						&regInfo.moduleCallbacks[0]);
				}

				targetPluginsList.push_back ( regInfo );

			} else {


				if (pluginType == PluginHandler) {
					it->extensions.insert(extList.begin(), extList.end());
					
					if (excludeExtList.size())
						regInfo.excludedExtensions.insert(excludeExtList.begin(), excludeExtList.end());
				
				} else {
					throw settings_load_error ("Module cannot be added twice, directory: %s",
					dirInfo.name.c_str());
				}

			}

		} 
		else if (util::equals(item->Value(), SettingsTags::RemoveElement) )
		{
			getAttrRes = item->QueryValueAttribute( SettingsTags::NameAttr, &name);
			if (getAttrRes == TIXML_NO_ATTRIBUTE)
				throw settings_load_error ("<%s> does not have \"%s\" attribute, directory: %s",
				SettingsTags::RegisterElement, SettingsTags::NameAttr, dirInfo.name.c_str());

			if (pluginType == PluginHandler)
			{
				loadStringAttribute (item, SettingsTags::ExtAttr, ext);

				if (!ext.empty()) {
					std::set<string> extList = HttpServerSettings::parseExtensions (ext);
					regInfo.extensions.insert(extList.begin(), extList.end());
					
					directory_plugins_list::iterator addElement = std::find_if (targetPluginsList.begin(), 
						targetPluginsList.end(), ahttp::PluginInfoByNameFinder(name) );
					
					// process 'remove' tag within current item
					if (addElement != targetPluginsList.end()) 
						addElement->excludedExtensions.insert(extList.begin(), extList.end());
				}
			}

			regInfo.registrationType = PluginRegistration::Remove;
			regInfo.pluginName = name;

			targetPluginsList.push_back ( regInfo );
		}
		else if (util::equals(item->Value(), SettingsTags::ClearElement) )
		{
			regInfo.registrationType = PluginRegistration::Clear;

			targetPluginsList.push_back ( regInfo );
		}


		item = item->NextSiblingElement ();


		ext.clear();
		name.clear();
		
		if (extList.size()) extList.clear();
		if (excludeExtList.size()) excludeExtList.clear();
	}
}

void SettingsLoader::loadDirectoryMappings (class TiXmlElement* mappingsElement, ahttp::DirectorySettings& dirInfo) throw (settings_load_error)
{
	using namespace ahttp;

	TiXmlElement* item = mappingsElement->FirstChildElement (SettingsTags::RegisterElement);

	while (item) 
	{
		TiXmlElement* reElem = item->FirstChildElement(SettingsTags::RegexElement);
		if (NULL == reElem)
			throw settings_load_error ("<%s> does not have <%s> child element, directory: %s",
			SettingsTags::RegisterElement, SettingsTags::RegexElement, dirInfo.name.c_str());

		string_constptr re = reElem->GetText();

		if ( aconnect::util::isNullOrEmpty(re) )
			throw settings_load_error ("<%s> element is empty, directory: %s",
			SettingsTags::RegexElement, dirInfo.name.c_str());

		TiXmlElement* urlElem = item->FirstChildElement(SettingsTags::UrlElement);
		if (NULL == urlElem)
			throw settings_load_error ("<%s> does not have <%s> child element, directory: %s",
			SettingsTags::RegisterElement, SettingsTags::UrlElement, dirInfo.name.c_str());


		string_constptr url = urlElem->GetText();
		if ( aconnect::util::isNullOrEmpty(url) )
			throw settings_load_error ("<%s> element is empty, directory: %s",
			SettingsTags::UrlElement, dirInfo.name.c_str());


		dirInfo.mappings.push_back( std::make_pair(boost::regex(re), url));

		item = item->NextSiblingElement (SettingsTags::RegisterElement);
	}
}



void SettingsLoader::tryLoadLocalSettings (ahttp::HttpServerSettings& settings, 
										   aconnect::string_constref filePath, 
										   ahttp::DirectorySettings& dirInfo) throw (settings_load_error)
{

	if (fs::exists (filePath))
	{
		TiXmlDocument doc( filePath.c_str() );

		if ( !doc.LoadFile() ) {
			throw settings_load_error ("Could not load local directory config file \"%s\". Error=\"%s\".",
				filePath.c_str(),
				doc.ErrorDesc());
		}

		TiXmlElement* root = doc.RootElement( );

		if ( !aconnect::util::equals(root->Value(), SettingsTags::DirectoryElement, false)  ) 
			throw settings_load_error ("Invalid local directory config file root element, file: %s",
			filePath.c_str());

		loadDirectorySettings (settings, root, dirInfo);
	}
}


//==================================================================//
//		Helpers														//
//==================================================================//

template<typename T>
bool SettingsLoader::loadIntAttribute (class TiXmlElement* elem, 
										   string_constptr attr, T &value) 
{
	T n;
	if ( elem->QueryValueAttribute (attr, &n) != TIXML_SUCCESS )
		return false;

	value = n;
	return true;
}

bool SettingsLoader::loadBoolAttribute (class TiXmlElement* elem, 
											string_constptr attr, bool &value) 
{
	string_constptr ptr = elem->Attribute (attr);
	if(!ptr)
		return false;

	value = aconnect::util::equals (ptr, SettingsTags::BooleanTrue)
		|| aconnect::util::equals (ptr, SettingsTags::BooleanYes);
	return true;
}

bool SettingsLoader::loadTristateAttribute (class TiXmlElement* elem, 
												string_constptr attr, 
												aconnect::Tristate::TristateEnum &value)
{
	bool boolAttr = false;
	if (loadBoolAttribute (elem, attr, boolAttr)) {
		value = boolAttr ? aconnect::Tristate::True : aconnect::Tristate::False;
		return true;
	}

	value = aconnect::Tristate::Undefined;
	return false;
}


bool SettingsLoader::loadStringAttribute (class TiXmlElement* elem, 
											  string_constptr attr, string &value, bool cannotBeEmpty ) 
{
	string_constptr ptr = elem->Attribute (attr);
	if(!ptr)
		return false;

	if(cannotBeEmpty && (ptr[0] == '\0'))
		return false;

	value = ptr;
	return true;
}
